﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BacteriumBattle
{

     
    enum BacteriumAction
    {
        ATTACK,
        CAPTURE,
        SUPPORT,
        IDLE
    }
    

    class Bacterium
    {
        private const int DEFAULT_INITIAL_POWER = 100;
        
        public int ID { get; private set; }
        public int TeamID { get; private set; }
        public int Power { get; private set; }
        public BacteriumAction CurrentAction { get; private set; }
        public Boolean IsAttacked { get; private set; }
        public Boolean IsSupported { get; private set; }

        private Observer Observer { get;  set; }

        private Random _fairDice = new Random();
 
        public Bacterium(int id, int teamID, Observer observer)
        {
            ID = id;
            TeamID = teamID;
            Power = teamID != 0 ? DEFAULT_INITIAL_POWER : 0;
            CurrentAction = BacteriumAction.IDLE;
            IsAttacked = false;
            IsSupported = false;

            Observer = observer;
            Observer.RegisterOnBattlefield(this);

           
        }

        #region ATTACK
        /// <summary>
        /// Attack request stub
        /// </summary>
        /// <param name="target"></param>
        private void Attack(Bacterium target)
        {
            if (CurrentAction != BacteriumAction.ATTACK)
	        {
                CurrentAction = BacteriumAction.ATTACK;
	        }

            target.TakeDamage();
        }
    
        /// <summary>
        /// Attack response stub
        /// </summary>
        public void TakeDamage()
        {
            if (Power > 0)
            {
                if (!IsAttacked)
                {
                    IsAttacked = true;
                }

                if (--Power <= 0)
                {
                    TeamID = 0;
                    IsAttacked = false;
                }

                Observer.UpdateProperty();//TODO: change notification
            }
            
            
        }
        #endregion

        #region CAPTURE
        /// <summary>
        /// Capture request stub
        /// </summary>
        /// <param name="target">For now it's the explicit object - in WS it's gonna be a Proxy object initialized by IP, received from the observer via ID</param>
        private void Capture(Bacterium target)
        {
            if (CurrentAction != BacteriumAction.CAPTURE)
            {
                CurrentAction = BacteriumAction.CAPTURE;
            }

            target.ChangeTeam(TeamID);
        }

        /// <summary>
        /// Capture response stub
        /// </summary>
        /// <param name="newTeamID"></param>
        public void ChangeTeam(int newTeamID)
        {
            if (TeamID == 0)
            {
                TeamID = newTeamID;
                //to avoid having zero power
                Power = _fairDice.Next(DEFAULT_INITIAL_POWER - 1) + 1;
                Observer.UpdateProperty();
            }
           
        }
        #endregion

        #region SUPPORT
        /// <summary>
        /// Support request stub
        /// </summary>
        /// <param name="target"></param>
        private void Support(Bacterium target)
        {
            //doesn't allow to heal hostile bacteria
            if (TeamID == target.TeamID)
            {
                if (CurrentAction != BacteriumAction.SUPPORT)
                {
                    CurrentAction = BacteriumAction.SUPPORT;
                }

                target.RestoreDamage();
            }
           
        }

        /// <summary>
        /// Support response stub
        /// </summary>
        public void RestoreDamage()
        {
            
            if (Power < 100)
            {
                if (!IsSupported)
                {
                    IsSupported = true;
                }

                Power++;
                Observer.UpdateProperty();//TODO: change notification
            }
                
            
            

        }
        #endregion

        #region IDLE
        /// <summary>
        /// This is a local action, so no requests needed
        /// </summary>
        private void Idle()
        {
            if (Power < 100)
            {
                if (CurrentAction != BacteriumAction.IDLE)
                {
                    CurrentAction = BacteriumAction.IDLE;
                }
                Power++;
            }
        }
        #endregion


        /// <summary>
        /// Decision Making
        /// </summary>
        public void PerformAction()
        {
            //dead bacteria cannot act
            if (Power > 0)
            {
                //grab neighbors from observer
                var neighbors = Observer.GetNeighbors(this);

                //looks for neutrals
                var potentialTargets = neighbors.Where(b => b.TeamID == 0);

                if (potentialTargets.Count() > 0)
                {
                    Capture(potentialTargets.First());
                }
                else
                {
                    var allies = neighbors.Where(b => b.TeamID == TeamID);
                   
                    //supposedly that there are no more neutral bacteria
                    var enemies = neighbors.Where(b => b.TeamID != TeamID);

                    var weakestEnemy = enemies.OrderBy(b => b.Power).First();

                    //should consider thyself as well
                    if (allies.Count() + 1 > enemies.Count())
                    {
                        Attack(weakestEnemy);
                    }
                    //otherwise there is a higher or equal number of enemies
                    else 
                    {
                        var  sumAlliedPower = allies.Sum(b => b.Power) + Power;
                        var  sumEnemyPower = enemies.Sum(b => b.Power);
                        
                        if (sumAlliedPower > sumEnemyPower)
                        {
                            Attack(weakestEnemy);
                        }
                        
                        //if outnumbered by power it might still attack
                        else if (_fairDice.NextDouble() < 0.5)
                        {
                            Attack(weakestEnemy);
                        }

                        //if outnumbered but healthy - help friends
                        else if (Power == 100)
                        {
                            //todo: proper null pointer handling
                            if (allies.Count() > 0)
                            {
                                var weakestAlly = allies.OrderBy(b => b.Power).First();
                                Support(weakestAlly);
                            }
                            
                        }

                        //if alone and bleeding, sit and wait
                        else
                        {
                            Idle();
                        }
 
                    }
                }

            }
           
            
        }
    }
}
